﻿namespace MetaProperties.Core
{
    using System;
    using System.ComponentModel;

    using MetaProperties.Framework;

    /// <summary>
    /// Defines a typed MetaProperty.
    /// </summary>
    /// <typeparam name="T">The type of the property.</typeparam>
    public class MetaProperty<T> : MetaProperty, IMetaProperty<T>
    {
        /// <summary>
        /// The synchronization object for attaching to events.
        /// </summary>
        private readonly object syncRoot = new object();

        /// <summary>
        /// The source container.
        /// </summary>
        private readonly MetaProperties.Framework.IObservable<T> container;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaProperty&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public MetaProperty(MetaProperties.Framework.IObservable<T> container)
            : base(container)
        {
            this.container = container;
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public override event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.PropertyChangedInner == null)
                    {
                        this.container.PropertyChanged += this.Container_PropertyChanged;
                    }

                    this.PropertyChangedInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.PropertyChangedInner -= value;

                    if (this.PropertyChangedInner == null)
                    {
                        this.container.PropertyChanged -= this.Container_PropertyChanged;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value is changing.
        /// </summary>
        public override event PropertyChangingEventHandler PropertyChanging
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.PropertyChangingInner == null)
                    {
                        this.container.PropertyChanging += this.Container_PropertyChanging;
                    }

                    this.PropertyChangingInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.PropertyChangingInner -= value;

                    if (this.PropertyChangingInner == null)
                    {
                        this.container.PropertyChanging -= this.Container_PropertyChanging;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public override event ValueChangedEventHandler UntypedValueChanged
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.UntypedValueChangedInner == null)
                    {
                        this.container.UntypedValueChanged += this.Container_UntypedValueChanged;
                    }

                    this.UntypedValueChangedInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.UntypedValueChangedInner -= value;

                    if (this.UntypedValueChangedInner == null)
                    {
                        this.container.UntypedValueChanged -= this.Container_UntypedValueChanged;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        public override event ValueChangedEventHandler UntypedValueSet
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.UntypedValueSetInner == null)
                    {
                        this.container.UntypedValueSet += this.Container_UntypedValueSet;
                    }

                    this.UntypedValueSetInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.UntypedValueSetInner -= value;

                    if (this.UntypedValueSetInner == null)
                    {
                        this.container.UntypedValueSet -= this.Container_UntypedValueSet;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueChanged
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.ValueChangedInner == null)
                    {
                        this.container.ValueChanged += this.Container_ValueChanged;
                    }

                    this.ValueChangedInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.ValueChangedInner -= value;

                    if (this.ValueChangedInner == null)
                    {
                        this.container.ValueChanged -= this.Container_ValueChanged;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        public event ValueChangedEventHandler<T> ValueSet
        {
            add
            {
                lock (this.syncRoot)
                {
                    if (this.ValueSetInner == null)
                    {
                        this.container.ValueSet += this.Container_ValueSet;
                    }

                    this.ValueSetInner += value;
                }
            }

            remove
            {
                lock (this.syncRoot)
                {
                    this.ValueSetInner -= value;

                    if (this.ValueSetInner == null)
                    {
                        this.container.ValueSet -= this.Container_ValueSet;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        private event PropertyChangedEventHandler PropertyChangedInner;

        /// <summary>
        /// Occurs when a property value is changing.
        /// </summary>
        private event PropertyChangingEventHandler PropertyChangingInner;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        private event ValueChangedEventHandler UntypedValueChangedInner;

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        private event ValueChangedEventHandler UntypedValueSetInner;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        private event ValueChangedEventHandler<T> ValueChangedInner;

        /// <summary>
        /// Occurs when a property value is set.
        /// </summary>
        private event ValueChangedEventHandler<T> ValueSetInner;

        /// <summary>
        /// Gets the value of the observable property.
        /// </summary>
        /// <value>The value of the observable property</value>
        public T Value
        {
            get { return this.container.Value; }
        }

        /// <summary>
        /// Gets the type of the property.
        /// </summary>
        /// <value>The type of the property.</value>
        public override Type PropertyType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// Handles the PropertyChanging event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangingEventArgs"/> instance containing the event data.</param>
        private void Container_PropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            var handler = this.PropertyChangingInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((PropertyChangingEventHandler)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecutePropertyChangingDelegate);
                }
            }
        }

        /// <summary>
        /// Handles the PropertyChanged event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void Container_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChangedInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((PropertyChangedEventHandler)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecutePropertyChangedDelegate);
                }
            }
        }

        /// <summary>
        /// Handles the UntypedValueSet event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs"/> instance containing the event data.</param>
        private void Container_UntypedValueSet(object sender, ValueChangedEventArgs e)
        {
            var handler = this.UntypedValueSetInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((ValueChangedEventHandler)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecuteUntypedValueChangedDelegate);
                }
            }
        }

        /// <summary>
        /// Handles the UntypedValueChanged event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs"/> instance containing the event data.</param>
        private void Container_UntypedValueChanged(object sender, ValueChangedEventArgs e)
        {
            var handler = this.UntypedValueChangedInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((ValueChangedEventHandler)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecuteUntypedValueChangedDelegate);
                }
            }
        }

        /// <summary>
        /// Handles the ValueSet event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs&lt;T&gt;"/> instance containing the event data.</param>
        private void Container_ValueSet(object sender, ValueChangedEventArgs<T> e)
        {
            var handler = this.ValueSetInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((ValueChangedEventHandler<T>)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecuteValueChangedDelegate);
                }
            }
        }

        /// <summary>
        /// Handles the ValueChanged event of the Container control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="MetaProperties.Framework.ValueChangedEventArgs&lt;T&gt;"/> instance containing the event data.</param>
        private void Container_ValueChanged(object sender, ValueChangedEventArgs<T> e)
        {
            var handler = this.ValueChangedInner;
            if (handler != null)
            {
                if (this.FilterCount == 0)
                {
                    if (!IsolatingEventFilter.IsActive)
                    {
                        handler(this, e);
                    }
                    else
                    {
                        Delegate[] subHandlers = handler.GetInvocationList();
                        int subHandlerCount = subHandlers.Length;

                        for (int i = 0; i < subHandlerCount; i++)
                        {
                            try
                            {
                                ((ValueChangedEventHandler<T>)subHandlers[i])(sender, e);
                            }
                            catch (Exception t)
                            {
                                if (!IsolatingEventFilter.HandleException(t))
                                {
                                    throw;
                                }
                            }
                        }
                    }
                }
                else
                {
                    IsolatingFilterPipeline.Execute(handler, this.Filters, this, e, this.ExecuteValueChangedDelegate);
                }
            }
        }

        /// <summary>
        /// Executes the property changing delegate.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="d">The event handler.</param>
        private void ExecutePropertyChangingDelegate(object sender, EventArgs args, Delegate d)
        {
            ((PropertyChangingEventHandler)d)(sender, (PropertyChangingEventArgs)args);
        }

        /// <summary>
        /// Executes the property changed delegate.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="d">The event handler.</param>
        private void ExecutePropertyChangedDelegate(object sender, EventArgs args, Delegate d)
        {
            ((PropertyChangedEventHandler)d)(sender, (PropertyChangedEventArgs)args);
        }

        /// <summary>
        /// Executes the untyped value changed delegate.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="d">The event handler.</param>
        private void ExecuteUntypedValueChangedDelegate(object sender, EventArgs args, Delegate d)
        {
            ((ValueChangedEventHandler)d)(sender, (ValueChangedEventArgs)args);
        }

        /// <summary>
        /// Executes the value changed delegate.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="d">The event handler.</param>
        private void ExecuteValueChangedDelegate(object sender, EventArgs args, Delegate d)
        {
            ((ValueChangedEventHandler<T>)d)(sender, (ValueChangedEventArgs<T>)args);
        }
    }
}
